<

バックグラウンドで JSON を解析する

デフォルトでは、Dart アプリはすべての作業を単一のスレッドで実行します。 多くの場合、このモデルはコーディングを簡素化し、十分に高速です アプリのパフォーマンスの低下やアニメーションの途切れが発生しないこと、 よく「ジャンク」と呼ばれます。

ただし、コストのかかる計算を実行する必要がある場合があります。 非常に大きな JSON ドキュメントの解析など。 この作業に 16 ミリ秒以上かかる場合は、 ユーザーがジャンクを経験します。

ジャンクを回避するには、負荷の高い計算を実行する必要があります 背景ではこんな感じ。 Android では、これは別のスレッドで作業をスケジュールすることを意味します。 Flutter では、別の隔離する。 このレシピでは次の手順を使用します。

  1. を追加します。httpパッケージ。
  2. を使用してネットワーク リクエストを実行します。httpパッケージ。
  3. 応答を写真のリストに変換します。
  4. この作業を別の単離物に移動します。

1.httpパッケージ

まず、httpプロジェクトにパッケージ化します。 のhttpパッケージによりネットワークの実行が容易になります JSON エンドポイントからのデータのフェッチなどのリクエスト。

追加するには、httpパッケージを依存関係として、 走るflutter pub add:

$ flutter pub add http

2. ネットワークリクエストを行う

この例では、大きな JSON ドキュメントをフェッチする方法について説明します。 これには、JSONプレースホルダーREST API、 を使用してhttp.get()方法。

Future<http.Response> fetchPhotos(http.Client client) async {
  return client.get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));
}

3. JSON を解析して写真のリストに変換します

次に、担当者の指導に従って、インターネットからデータを取得するレシピ、 を変換しますhttp.ResponseDart オブジェクトのリストに追加します。 これにより、データの操作が容易になります。

を作成しますPhotoクラス

まず、Photo写真に関するデータを含むクラス。 を含めるfromJson()ファクトリメソッドを使用すると、PhotoJSON オブジェクトから始まります。

class Photo {
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;

  const Photo({
    required this.albumId,
    required this.id,
    required this.title,
    required this.url,
    required this.thumbnailUrl,
  });

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}

応答を写真のリストに変換します

次に、次の手順に従って、fetchPhotos()を返す関数Future<List<Photo>>:

  1. を作成しますparsePhotos()応答を変換する関数 体をList<Photo>
  2. 使用parsePhotos()の機能fetchPhotos()関数。
// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return parsePhotos(response.body);
}

4. この作業を別の分離に移動します

を実行すると、fetchPhotos()遅いデバイスでは機能しませんが、 解析中にアプリが短時間フリーズすることに気づくかもしれません。 JSONを変換します。これはジャンクなので、削除する必要があります。

解析と変換を移動することでジャンクを削除できます を使用して背景を分離しますcompute()Flutterが提供する関数です。のcompute()関数の実行コストが高い バックグラウンドの関数は分離して結果を返します。この場合、 を実行しますparsePhotos()バックグラウンドでの機能。

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}

分離株の操作に関する注意事項

アイソレータはメッセージをやり取りすることによって通信します。これらのメッセージは、 などのプリミティブな値にするnullnumbooldouble、 またString、 また などの単純なオブジェクトList<Photo>この例では。

より複雑なオブジェクトを渡そうとすると、エラーが発生する可能性があります。 などFutureまたhttp.Response分離株の間。

別の解決策として、以下を確認してください。worker_managerまたworkmanagerバックグラウンド処理用のパッケージ。

完全な例

import 'dart:async';
import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

Future<List<Photo>> fetchPhotos(http.Client client) async {
  final response = await client
      .get(Uri.parse('https://jsonplaceholder.typicode.com/photos'));

  // Use the compute function to run parsePhotos in a separate isolate.
  return compute(parsePhotos, response.body);
}

// A function that converts a response body into a List<Photo>.
List<Photo> parsePhotos(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();

  return parsed.map<Photo>((json) => Photo.fromJson(json)).toList();
}

class Photo {
  final int albumId;
  final int id;
  final String title;
  final String url;
  final String thumbnailUrl;

  const Photo({
    required this.albumId,
    required this.id,
    required this.title,
    required this.url,
    required this.thumbnailUrl,
  });

  factory Photo.fromJson(Map<String, dynamic> json) {
    return Photo(
      albumId: json['albumId'] as int,
      id: json['id'] as int,
      title: json['title'] as String,
      url: json['url'] as String,
      thumbnailUrl: json['thumbnailUrl'] as String,
    );
  }
}

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    const appTitle = 'Isolate Demo';

    return const MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: FutureBuilder<List<Photo>>(
        future: fetchPhotos(http.Client()),
        builder: (context, snapshot) {
          if (snapshot.hasError) {
            return const Center(
              child: Text('An error has occurred!'),
            );
          } else if (snapshot.hasData) {
            return PhotosList(photos: snapshot.data!);
          } else {
            return const Center(
              child: CircularProgressIndicator(),
            );
          }
        },
      ),
    );
  }
}

class PhotosList extends StatelessWidget {
  const PhotosList({super.key, required this.photos});

  final List<Photo> photos;

  @override
  Widget build(BuildContext context) {
    return GridView.builder(
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 2,
      ),
      itemCount: photos.length,
      itemBuilder: (context, index) {
        return Image.network(photos[index].thumbnailUrl);
      },
    );
  }
}

Isolate demo